/**
 * This file is part of CommaTuner.
 * Copyright 2011 Gabriel G Mendonça (gabrielgmendonca@gmail.com)
 * 
 * CommaTuner is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * CommaTuner is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with CommaTuner.  If not, see <http://www.gnu.org/licenses/>.
 */

package br.mendonca.audio.analysis;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

/**
 * The PitchDetector class reads the input from the cellphone's microphone and
 * identifies the relevant pitch being produced.
 * 
 * @author gmendonca
 */
public class PitchDetector {
	private KissFFT fft;
	private ByteBuffer samples;
	private FloatBuffer spectrum;
	private AudioRecorder recorder;
	private int sampleRate;
	private int bufferSize;

	/**
	 * Constructor.
	 * 
	 * @param sampleRate
	 *            The sample rate (e.g. 44100 Hz)
	 * @param bufferSize
	 *            The size of the buffer. Must be a power of 2.
	 */
	public PitchDetector(int sampleRate, int bufferSize) {
		fft = new KissFFT(bufferSize);
		samples = allocateByteBuffer(bufferSize);
		spectrum = allocateFloatBuffer(bufferSize / 2 + 1);

		recorder = new AudioRecorder(sampleRate, bufferSize * 2);
		recorder.start();

		this.sampleRate = sampleRate;
		this.bufferSize = bufferSize;
	}

	/**
	 * Identifies the relevant pitch from the mic's input.
	 * 
	 * @return The identified pitch.
	 */
	public Pitch detect() {
		recorder.read(samples, bufferSize);
		fft.spectrum(samples, spectrum);

		Pitch pitch = getPitch(spectrum);

		return pitch;
	}

	/**
	 * Releases the allocated memory.
	 */
	public void destroy() {
		fft.dispose();
		recorder.destroy();
	}

	private Pitch getPitch(FloatBuffer spectrum) {
		float peak = 0;
		float maxEnergy = 0;

		/* Searches for the peak, the FFT bin with more energy. */
		for (int i = 0; i < (bufferSize / 2 + 1); i++) {
			float binEnergy = spectrum.get(i);

			if (binEnergy > maxEnergy) {
				maxEnergy = binEnergy;
				peak = i;
			}
		}

		float resolution = (float) sampleRate / (float) bufferSize;
		float frequency = (peak + 0.5f) * resolution;

		Pitch pitch = new Pitch(frequency, resolution);

		return pitch;
	}

	private ByteBuffer allocateByteBuffer(int numSamples) {
		ByteBuffer buffer = ByteBuffer.allocateDirect(numSamples * 2);
		buffer.order(ByteOrder.nativeOrder());
		return buffer;
	}

	private FloatBuffer allocateFloatBuffer(int numSamples) {
		ByteBuffer buffer = allocateByteBuffer(numSamples * 2);
		return buffer.asFloatBuffer();
	}

	/* Loads the native KissFFT library. */
	static {
		System.loadLibrary("kissfft");
	}
}
